// deno-lint-ignore-file no-await-in-loop
/* This file parses sites.yml, fetches GH metadata like contributors
and stars for each site, then writes the results to site/src/sites.yml. */

import type { Site } from '$lib'
import yaml from 'js-yaml'
import 'jsr:@std/dotenv/load'
import { marked } from 'marked'
import fs from 'node:fs'
import { performance } from 'node:perf_hooks'
import process from 'node:process'
import type { Action } from './'

export function title_to_slug(title: string): string {
  return title.toLowerCase().replaceAll(` `, `-`)
}

export async function fetch_github_metadata(options: { action?: Action } = {}) {
  const { action = `add-missing` } = options
  const in_path = `../sites.yml`
  const out_path = `../site/src/sites.yml`

  const sites = yaml.load(fs.readFileSync(in_path)) as Site[]

  const old_sites = fs.existsSync(out_path) ? yaml.load(fs.readFileSync(out_path)) : []

  const this_file = import.meta.url.split(`/`).pop()

  console.log(`Running ${this_file}...`)

  const start = performance.now()

  const old_slugs = old_sites.map((site) => site.slug)

  const seen_sites: string[] = []
  const skipped_sites: string[] = []
  const updated_sites: string[] = []

  if (!process.env.GH_TOKEN) {
    console.error(`GH_TOKEN environment variable is not set.`)
    process.exit(1)
  }

  const headers = {
    authorization: `token ${process.env.GH_TOKEN}`,
  }

  async function fetch_check(url: string) {
    const response = await fetch(url, { headers }).then((res) => res.json())
    if (response.message) throw new Error(response.message)
    return response
  }

  function https_url(url: string) {
    if (!url) return null
    if (url.startsWith(`http`)) return url.replace(`http://`, `https://`)
    return `https://${url}`
  }

  // Only update site/src/sites.js if a new site was added to sites.yml
  // or repo stars were last fetched more than a month ago.
  for (const site of sites) {
    const slug = title_to_slug(site.title)

    if (seen_sites.includes(slug)) throw new Error(`Duplicate slug ${slug}`)
    else seen_sites.push(slug)

    site.slug = slug

    // add open-source tag for all sites with repo key
    if (site.repo && !site.tags.includes(`open source`)) {
      site.tags.push(`open source`)
      site.tags.sort((a, b) => a.localeCompare(b)) // sort tags alphabetically in place
    }

    // skip site if it doesn't have a repo key or if data was fetched for it before
    // and update_existing is false
    if (
      !site.repo ||
      (old_slugs.includes(slug) && action !== `update-existing`)
    ) {
      skipped_sites.push(slug)
      continue
    }

    // also skip site if repo key cannot be parsed into a user login and a repo name
    const repoHandle = site.repo.split(`github.com/`)[1]
    if (!repoHandle || repoHandle.split(`/`).length !== 2) {
      console.error(`bad repo handle ${repoHandle}`)
      skipped_sites.push(slug)
      continue
    }

    // fetch stars
    try {
      const url = `https://api.github.com/repos/${repoHandle}`
      const repo = await fetch_check(url)
      site.repo_stars = repo.stargazers_count
    } catch (error) {
      console.error(`Error fetching stars for ${site.title}:`, error)
    }

    // fetch most active contributors
    let contributors = await fetch_check(
      `https://api.github.com/repos/${repoHandle}/contributors`,
    )

    // show at most 5 contributors and only those with more than 10 commits
    // and of type 'User' (to filter out bots) sorted by number of contributions
    contributors = contributors
      .filter((itm) => itm.contributions > 10 && itm.type === `User`)
      .sort((c1, c2) => c2.contributions - c1.contributions)
      .slice(0, 5)

    contributors = await Promise.all(
      contributors.map((person) =>
        fetch(person.url, { headers }).then((res) => res.json())
      ),
    )

    site.contributors = contributors.map(
      ({ name, location, company, ...c }) => ({
        github: c.login,
        twitter: c.twitter_username,
        url: https_url(c.blog),
        avatar: c.avatar_url,
        name,
        location,
        company,
      }),
    )

    updated_sites.push(slug)
  }

  const new_sites = sites.map((site) => {
    const old_site = old_sites.find((old) => old.slug === site.slug) ?? {}
    // retain fetched GitHub data from old_sites in case we didn't refetch
    // but overwrite with new data if we did
    for (const key of [`repo_stars`, `contributors`]) {
      if (site[key] === undefined) site[key] = old_site[key]
    }

    site.description = marked.parseInline(site.description)

    return site
  })

  const wall_time = ((performance.now() - start) / 1000).toFixed(2)

  const comment = `# auto-generated by ${this_file}\n\n`
  fs.writeFileSync(out_path, comment + yaml.dump(new_sites))

  console.log(
    `${this_file} took ${wall_time}s, updated ${updated_sites.length}, ` +
      `skipped ${skipped_sites.length}\n`,
  )
}

if (import.meta.main) {
  await fetch_github_metadata()
}
